package com.hfzy.ihk.common.core.elasticsearch;


import com.alibaba.fastjson.JSON;
import com.hfzy.ihk.common.core.elasticsearch.base.DbOperation;
import com.hfzy.ihk.common.core.elasticsearch.entity.VersionCtrlErrorMsg;
import com.hfzy.ihk.common.core.elasticsearch.enums.VersionOperation;
import com.hfzy.ihk.common.facade.elasticsearch.ElasticsearchVersionRedisKey;
import com.hfzy.ihk.common.facade.elasticsearch.entity.ElasticsearchTable;
import com.hfzy.ihk.common.core.elasticsearch.repository.common.VersionCtrlErrorMsgRepository;
import org.elasticsearch.common.UUIDs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Objects;

/**
 * Created by Administrator on 2018/5/28.
 *
 *  和elasticsearch本身的版本控制相比，
 *  在业务逻辑中，其实需要更细粒度的控制，比如实体类中的一个List字段，或是实体类中的一部分字段。
 *  可以由自己很灵活的定义这个版本号控制那些字段
 *
 * @author wws
 */

public abstract class ElasticsearchSupport {

    private static Logger logger = LoggerFactory.getLogger(ElasticsearchSupport.class);

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    @Lazy
    VersionCtrlErrorMsgRepository versionCtrlErrorMsgRepository;

    public boolean createDataAndSetVersion(ElasticsearchVersionRedisKey versionKey, ElasticsearchTable optTable, String dataId, Long currentDataVersion, DbOperation dbOpt){
        //维护版本
        redisTemplate.opsForHash().put(versionKey.getVersionKeyName(),dataId,currentDataVersion);

        try {
            dbOpt.todo();
            logger.debug("create success,versionKey:dataId->{}:{},currentDataVersion:{}",versionKey.getVersionKeyName(),dataId,currentDataVersion);
            return true;
        } catch (Exception e) {
            logger.error("create fail,versionKey:dataId->{}:{},currentDataVersion:{}",versionKey.getVersionKeyName(),dataId,currentDataVersion);
            try {
                //如果这部失败，成为脏数据
                redisTemplate.opsForHash().delete(versionKey.getVersionKeyName(),dataId);
            } catch (Exception e1) {
                VersionCtrlErrorMsg versionCtrlErrorMsg = new VersionCtrlErrorMsg();
                versionCtrlErrorMsg.setId(UUIDs.base64UUID());
                versionCtrlErrorMsg.setRedisKey(versionKey.getVersionKeyName());
                versionCtrlErrorMsg.setDataId(dataId);
                versionCtrlErrorMsg.setOptTable(optTable.getTableName());
                versionCtrlErrorMsg.setVersionOpt(VersionOperation.deleteVersion.getOperationName());
                //redis和ES同时挂了？阿弥陀佛...
                try {
                    versionCtrlErrorMsgRepository.save(versionCtrlErrorMsg);
                } catch (Exception e2) {
                    //放内存
                    ElasticsearchLocalCache.put(versionCtrlErrorMsg);
                }
            }

        }
        return false;
    }

    public boolean checkVersionAndUpdateData(ElasticsearchVersionRedisKey versionKey, ElasticsearchTable optTable, String dataId, Long currentDataVersion, DbOperation dbOpt){

        /**数据版本自增*/
        currentDataVersion ++;
        //维护版本
        Long dbVersion = redisTemplate.opsForHash().increment(versionKey.getVersionKeyName(),dataId,1L);

        if(Objects.equals(currentDataVersion, dbVersion)) {

            try {
                dbOpt.todo();
                logger.debug("update success,versionKey:dataId->{}:{},currentDataVersion:{}",versionKey.getVersionKeyName(),dataId,currentDataVersion);
                return true;
            } catch (Exception e) {
                logger.error("update fail,versionKey:dataId->{}:{},currentDataVersion:{}",versionKey.getVersionKeyName(),dataId,currentDataVersion);
                rollbackDbVersion(versionKey,optTable,dataId);
            }

        }else if(currentDataVersion < dbVersion){

            //多线程并发下，必须回滚版本号
            rollbackDbVersion(versionKey,optTable,dataId);
            logger.error("回滚version,versionKey:dataId->{}:{},currentDataVersion:{},dbVersion:{}",versionKey.getVersionKeyName(),dataId,currentDataVersion,dbVersion);

        }else if(currentDataVersion > dbVersion){
            //修正
//            redisTemplate.opsForHash().put(versionKey,dataId,currentDataVersion-1);//currentDataVersion-1才是数据真实的版本
            redisTemplate.opsForHash().put(versionKey.getVersionKeyName(),dataId,currentDataVersion);
            logger.warn("修正version,versionKey:dataId->{}:{},currentDataVersion:{},dbVersion:{}",versionKey.getVersionKeyName(),dataId,currentDataVersion,dbVersion);
            //更新
            try {
                dbOpt.todo();
                logger.debug("修正version后,update success,versionKey:dataId->{}:{},currentDataVersion:{}",versionKey.getVersionKeyName(),dataId,currentDataVersion);
                return true;
            } catch (Exception e) {
                logger.error("修正version后,update fail,versionKey:dataId->{}:{},currentDataVersion:{}",versionKey.getVersionKeyName(),dataId,currentDataVersion);
            }
        }

        return false;
    }

    private void rollbackDbVersion(ElasticsearchVersionRedisKey versionKey, ElasticsearchTable optTable, String dataId){
        try {
            redisTemplate.opsForHash().increment(versionKey.getVersionKeyName(),dataId,-1L);
        } catch (Exception e1) {
            logger.warn("rollbackDbVersion fail ,versionKey:dataId->{}:{}",versionKey.getVersionKeyName(),dataId);
            //redis和ES同时挂了？阿弥陀佛...
            VersionCtrlErrorMsg versionCtrlErrorMsg = new VersionCtrlErrorMsg();
            versionCtrlErrorMsg.setId(UUIDs.base64UUID());
            versionCtrlErrorMsg.setRedisKey(versionKey.getVersionKeyName());
            versionCtrlErrorMsg.setDataId(dataId);
            versionCtrlErrorMsg.setOptTable(optTable.getTableName());
            versionCtrlErrorMsg.setVersionOpt(VersionOperation.rollbackVersion.getOperationName());
            versionCtrlErrorMsg.setRollbackValue(1);
            try {
                versionCtrlErrorMsgRepository.save(versionCtrlErrorMsg);
            } catch (Exception e2) {
                ElasticsearchLocalCache.put(versionCtrlErrorMsg);
                logger.error("redis,ES 可能已崩溃。versionMsg:{}", JSON.toJSONString(versionCtrlErrorMsg));
            }
        }
    }

}
